package il.ac.technion.wiki.impl.parsers;

import il.ac.technion.wiki.api.expressions.AndOp;
import il.ac.technion.wiki.api.expressions.BinaryOperation;
import il.ac.technion.wiki.api.expressions.Expression;
import il.ac.technion.wiki.api.expressions.NotOp;
import il.ac.technion.wiki.api.expressions.OrOp;
import il.ac.technion.wiki.api.expressions.Term;
import il.ac.technion.wiki.api.expressions.UnaryOperation;
import il.ac.technion.wiki.api.parsers.IQueryParser;

import java.util.Arrays;
import java.util.Stack;

/**
 *	Concrete query parser that build an expression tree from the given query.
 *	Uses Expression factory to determine the type of the expression
 */
public class WikiQueryParser implements IQueryParser {
	@Override
	public Expression parse(String q) {
		String[] queryArray = q.split(" ");
		return recParse(queryArray, 0);
	}

	private Expression recParse(String[] queryArray, int idx) {
		Stack<Expression> stack = new Stack<Expression>();
		for (int i = idx; i < queryArray.length; ++i) {
			Expression expr = ExpressionFactory.createExpression(queryArray[i]);

			if (expr instanceof NotOp) {
				UnaryOperation uo = (UnaryOperation) expr;
				if (i + 1 >= queryArray.length)
					throw new ArrayIndexOutOfBoundsException();
				if ((queryArray[i+1].startsWith("("))){
					int endIdx = handleParenthesis(queryArray, i);
					Expression myExpr = recParse(Arrays.copyOfRange(queryArray, i+1, endIdx+1), 0);
					uo.setElement(myExpr);
					i = endIdx;
				} else
					uo.setElement(ExpressionFactory.createExpression(queryArray[i+1]));
				++i;
			} else if (expr instanceof BinaryOperation) {
				BinaryOperation bo = (BinaryOperation) expr;
				if (i + 1 >= queryArray.length)
					throw new ArrayIndexOutOfBoundsException();
				if ((queryArray[i+1].startsWith("("))){
					int endIdx = handleParenthesis(queryArray, i);
					Expression myExpr = recParse(Arrays.copyOfRange(queryArray, i+1, endIdx+1), 0);
					bo.setElements(stack.pop(), myExpr);
					i = endIdx;
				} else
					bo.setElements(stack.pop(),ExpressionFactory.createExpression(queryArray[i+1]));
				++i;
			}
			stack.push(expr);
		}
		if (stack.size() != 1)
			throw new RuntimeException("Stack size different than 1");
		return stack.pop();
	}

	private int handleParenthesis(String[] queryArray, int i) {
		int endIdx = findMatchingParanthesis(queryArray, i + 1);
		if (endIdx == -1)
			throw new RuntimeException("No matching parenthesis were found");
		queryArray[i+1] = queryArray[i+1].substring(1); // chop first
		queryArray[endIdx] = queryArray[endIdx].substring(0,
				queryArray[endIdx].length() - 1); // chop last
		return endIdx;
	}

	private int findMatchingParanthesis(String[] queryArray, int startIdx) {
		int parCount = 0;
		for (int i = startIdx; i < queryArray.length; ++i) {
			if (queryArray[i].startsWith("("))
				parCount++;
			if (queryArray[i].endsWith(")"))
				parCount--;
			if (parCount == 0)
				return i;
		}
		return -1;
	}
	
	private static class ExpressionFactory {
		public static Expression createExpression(String word) {
			if (word.toLowerCase().equals("and"))
				return new AndOp();
			if (word.toLowerCase().equals("or"))
				return new OrOp();
			if (word.toLowerCase().equals("not"))
				return new NotOp();
			if (!word.startsWith("\""))
				throw new IllegalArgumentException("Illegal argument: " + word);
			return new Term(word.substring(1, word.length()-1)); // removing the quotes
		}
	}
}
